package com.tianji.promotion.handler;

import com.tianji.common.utils.CollUtils;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.service.ICouponService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;

/**
 * @author Covey
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class IssuePromotionHandler {

    private final ICouponService couponService;

    /**
     * 定时发放优惠券
     */
    @XxlJob("startIssueJob")
    public void startIssueJob() {
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();
        LocalDateTime now = LocalDateTime.now();
        log.info("开始执行优惠券发放开始任务，分片[{}/{}]，当前时间：{}",
                shardIndex + 1, shardTotal, now);
        try {
            // 1. 先查询符合条件的优惠券ID
            List<Coupon> coupons = getStartCoupons(shardIndex, shardTotal, now);
            if (CollUtils.isEmpty(coupons)) {
                log.info("没有需要开始发放的优惠券，当前时间：{}", now);
                return;
            }
            // 2.批量更新
            couponService.startIssuePromotion(coupons, now);
            log.info("优惠券发放任务执行完成，共发放{}张优惠券", coupons.size());
        } catch (Exception e) {
            log.error("优惠券发放任务执行失败", e);
            throw e;
        }
    }

    /**
     * 定时结束发放优惠券
     */
    @XxlJob("endIssueJob")
    public void endIssueJob() {
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();
        LocalDateTime now = LocalDateTime.now();
        log.info("开始执行优惠券发放结束任务，分片[{}/{}]，当前时间：{}",
                shardIndex + 1, shardTotal, now);
        try {
            // 1. 先查询符合条件的优惠券ID
            // 使用数据库级别的分片查询，避免内存分片的数据量限制
            List<Coupon> coupons = getEndCouponIds(shardIndex, shardTotal, now);
            if (CollUtils.isEmpty(coupons)) {
                log.info("没有需要结束发放的优惠券，当前时间：{}", now);
                return;
            }
            // 2.修改优惠券状态
            couponService.endIssuePromotion(coupons, now);
            log.info("成功结束发放{}张优惠券", coupons.size());
        } catch (Exception e) {
            log.error("优惠券结束任务执行失败", e);
            throw e;
        }
    }

    /**
     * 基于数据库分页的分片方案
     */
    private List<Coupon> getStartCoupons(int shardIndex, int shardTotal, LocalDateTime now) {
        // 参数验证
        if (shardIndex < 0 || shardTotal <= 0 || shardIndex >= shardTotal) {
            throw new IllegalArgumentException("分片参数错误: shardIndex=" + shardIndex + ", shardTotal=" + shardTotal);
        }
        if (shardTotal == 1) {
            // 单分片直接查询所有
            return couponService.lambdaQuery()
                    .eq(Coupon::getStatus, CouponStatus.UN_ISSUE)
                    .le(Coupon::getIssueBeginTime, now)
                    .gt(Coupon::getIssueEndTime, now)
                    .list();
        }
        // 多分片情况下使用模运算分片
        return couponService.lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.UN_ISSUE)
                .le(Coupon::getIssueBeginTime, now)
                .gt(Coupon::getIssueEndTime, now)
                .apply("MOD(id, {0}) = {1}", shardTotal, shardIndex)
                .list();
    }

    /**
     * 基于数据库分页的分片方案
     */
    private List<Coupon> getEndCouponIds(int shardIndex, int shardTotal, LocalDateTime now) {
        // 参数验证
        if (shardIndex < 0 || shardTotal <= 0 || shardIndex >= shardTotal) {
            throw new IllegalArgumentException("分片参数错误: shardIndex=" + shardIndex + ", shardTotal=" + shardTotal);
        }
        if (shardTotal == 1) {
            // 单分片直接查询所有
            return couponService.lambdaQuery()
                    .select(Coupon::getId)
                    .eq(Coupon::getStatus, CouponStatus.ISSUING)
                    .le(Coupon::getIssueEndTime, now)
                    .list();
        }
        // 多分片情况下使用模运算分片
        return couponService.lambdaQuery()
                .select(Coupon::getId)
                .eq(Coupon::getStatus, CouponStatus.ISSUING)
                .le(Coupon::getIssueEndTime, now)
                .apply("MOD(id, {0}) = {1}", shardTotal, shardIndex)
                .list();
    }
}
